package com.xyhy.starter.web.log;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.json.JSONUtil;
import lombok.NoArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.data.domain.Pageable;
import org.springframework.web.multipart.MultipartFile;
import com.xyhy.core.utils.web.IpUtil;
import com.xyhy.core.utils.web.ServletUtils;
import com.xyhy.core.web.annotation.LogIgnoreResult;
import com.xyhy.core.web.annotation.NoLog;
import com.xyhy.core.web.log.IRequestLogService;
import com.xyhy.core.web.log.RequestLogBean;
import com.xyhy.core.web.model.page.PageResult;

import jakarta.servlet.http.HttpServletRequest;

import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Aspect
@NoArgsConstructor
@ConditionalOnBean(IRequestLogService.class)
public class RequestLogAspect {
  private @Autowired IRequestLogService logService;

  @Around("@annotation(org.springframework.web.bind.annotation.GetMapping) ||" +
          "@annotation(org.springframework.web.bind.annotation.PostMapping) ||" +
          "@annotation(org.springframework.web.bind.annotation.PutMapping) ||" +
          "@annotation(org.springframework.web.bind.annotation.DeleteMapping) ||" +
          "@annotation(org.springframework.web.bind.annotation.RequestMapping)")
  public Object doMethodProcess(ProceedingJoinPoint point) throws Throwable {
    MethodSignature ms = (MethodSignature) point.getSignature();
    if (ms.getMethod().isAnnotationPresent(NoLog.class)) return point.proceed();
    if (point.getTarget().getClass().isAnnotationPresent(NoLog.class)) return point.proceed();

    return handler(point);
  }

  private Object handler(ProceedingJoinPoint point) throws Throwable {
    Object result = null;
    long startTime = System.nanoTime();
    RequestLogBean bean = new RequestLogBean();

    try {
      result = point.proceed();
    } catch (Throwable e) {
      bean.setError(true);
      bean.setException(e);
      throw e;
    } finally {
      insertLog(point, bean, startTime, result);
    }

    return result;
  }

  private void insertLog(ProceedingJoinPoint point, RequestLogBean bean, long startTime, Object result) {
    HttpServletRequest request = ServletUtils.getRequest();

    bean.setUrl(Objects.requireNonNull(request).getRequestURI());
    bean.setIp(IpUtil.getIpAddr());
    bean.setCostTime((int) (TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)));
    bean.setParams(getParams(point));
    bean.setResult(getResult(point, result));
    logService.insert(bean);
  }

  private String getParams(ProceedingJoinPoint point) {
    List<Object> data = Arrays.stream(point.getArgs()).filter(x -> !(x instanceof MultipartFile))
            .filter(x -> !(x instanceof File)).collect(Collectors.toList());
    return JSONUtil.toJsonStr(data);
  }

  private String getResult(ProceedingJoinPoint point, Object result) {
    MethodSignature ms = (MethodSignature) point.getSignature();
    if (ms.getMethod().isAnnotationPresent(LogIgnoreResult.class)) return null;
    if (point.getTarget().getClass().isAnnotationPresent(LogIgnoreResult.class)) return null;

    Class<?> returnType = ((MethodSignature) point.getSignature()).getReturnType();
    if (returnType.equals(PageResult.class)) return null;
    if (returnType.equals(Pageable.class)) return null;
    if (returnType.equals(List.class)) return null;
    if (returnType.equals(Collection.class)) return null;
    if (ClassUtil.isPrimitiveWrapper(returnType)) return String.valueOf(result);
    return JSONUtil.toJsonStr(result);
  }

}
